package com.codequirks.acceptance;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class AcceptanceTestRunner extends BlockJUnit4ClassRunner {
    private final String successColor = "green";
    private final String failureColor = "red";
    private final File reportFile;
    private String report = "";
    private static final String REPORT_OPEN_XML = "<html><body style=\"background-color: silver\">";
    private static final String REPORT_CLOSE_XML = "</body></html>";

    public AcceptanceTestRunner(Class<?> klass) throws InitializationError {
        super(klass);
        reportFile = openReportFile();
    }

    @Override
    protected List<FrameworkMethod> computeTestMethods() {
        List<FrameworkMethod> result = new ArrayList<FrameworkMethod>();
        for (FrameworkMethod method : getTestClass().getAnnotatedMethods(AcceptanceTest.class)) {
            result.add(method);
        }
        for (FrameworkMethod method : getTestClass().getAnnotatedMethods(Test.class)) {
            result.add(method);
        }
        return result;
    }

    @Override
    protected void runChild(FrameworkMethod method, RunNotifier notifier) {
        if (method.getAnnotation(AcceptanceTest.class) != null && reportFile != null) {
            runAcceptanceTest(method, notifier);
        } else {
            runTest(method, notifier);
        }
    }

    protected Statement childrenInvoker(final RunNotifier notifier) {
        final Statement runChildren = super.childrenInvoker(notifier);
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                prepareReport();
                report("\t<div><h1>" + getName() + "</h1><table cellpadding=\"0\" cellspacing=\"0\" width=\"100%\"><tr><th width=\"50%\">Scenario</th><th width=\"50%\">Guarantee</th></tr>\n");
                runChildren.evaluate();
                report("\t</table></div>\n");
                writeReport();
            }
        };
    }

    private void writeReport() throws Throwable {
        if (reportFile != null) {
            FileWriter writer = new FileWriter(reportFile);
            writer.write(REPORT_OPEN_XML + "\n");
            writer.write(report);
            writer.write(REPORT_CLOSE_XML + "\n");
            writer.close();
        }
    }

    private void prepareReport() throws Throwable {
        if (reportFile != null) {
            BufferedReader fileReader;
            try {
                fileReader = new BufferedReader(new FileReader(reportFile));
                String nextLine;
                while((nextLine = fileReader.readLine()) != null) {
                    if(! nextLine.equals(REPORT_OPEN_XML) && ! nextLine.equals(REPORT_CLOSE_XML) ) {
                        report += nextLine + "\n";
                    }
                }
            } catch(FileNotFoundException ignored) {
            }
        }
    }

    private void report(String s) {
        report += s;
    }

    private File openReportFile() {
        String fileLocation = System.getProperty("acceptanceTestReportFile");
        if (fileLocation != null && !fileLocation.equals("")) {
            return new File(fileLocation);
        } else {
            return null;
        }
    }

    private void runAcceptanceTest(FrameworkMethod method, RunNotifier notifier) {
        EachTestNotifier eachNotifier = makeNotifier(method, notifier);
        if (method.getAnnotation(Ignore.class) != null) {
            eachNotifier.fireTestIgnored();
            return;
        }

        AcceptanceTest testAnnotation = method.getAnnotation(AcceptanceTest.class);

        eachNotifier.fireTestStarted();
        try {
            methodBlock(method).evaluate();
            report("<tr style=\"height: 25px; padding-top:5px; background-color:" + successColor + ";\">");
        } catch (AssumptionViolatedException e) {
            eachNotifier.addFailedAssumption(e);
            report("<tr style=\"height: 25px; padding-top:5px; background-color:" + failureColor + ";\">");
        } catch (Throwable e) {
            eachNotifier.addFailure(e);
            report("<tr style=\"height: 25px; padding-top:5px; background-color:" + failureColor + ";\">");
        } finally {
            eachNotifier.fireTestFinished();
        }
        report("<td><strong>" + testAnnotation.scenario() + "</strong></td><td><strong>" + testAnnotation.guarantee() + "</strong></td></tr>");
    }

    private void runTest(FrameworkMethod method, RunNotifier notifier) {
        EachTestNotifier eachNotifier = makeNotifier(method, notifier);
        if (method.getAnnotation(Ignore.class) != null) {
            eachNotifier.fireTestIgnored();
            return;
        }

        eachNotifier.fireTestStarted();
        try {
            methodBlock(method).evaluate();
        } catch (AssumptionViolatedException e) {
            eachNotifier.addFailedAssumption(e);
        } catch (Throwable e) {
            eachNotifier.addFailure(e);
        } finally {
            eachNotifier.fireTestFinished();
        }
    }

    private EachTestNotifier makeNotifier(FrameworkMethod method,
                                          RunNotifier notifier) {
        Description description = describeChild(method);
        return new EachTestNotifier(notifier, description);
    }
}
